package com.tmt.helper;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * Created by ThreeManTeam on 2017/6/1.
 */
public class PackageHelper {


    private static final String fileSeparator = System.getProperty("file.separator");

    /**
     * 获取某包下（包括该包的所有子包）所有类
     *
     * @param packageName 包名
     * @return 类的完整名称
     */
    public static List<String> getClassName(String packageName) {
        return getClassName(packageName, true, true, 1);
    }

    /**
     * 获取某包下所有类
     *
     * @param packageName  包名
     * @param childPackage 是否遍历子包
     * @param scanJar      是否遍历到jar包中
     * @param deepth       遍历jar包的深度。
     * @return 类的完整名称
     */
    public static List<String> getClassName(String packageName, boolean childPackage, boolean scanJar, int deepth) {
        List<String> fileNames = new ArrayList<>();

        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        String packagePath = packageName.replace(".", "/");
        try {
            Enumeration<URL> urls = loader.getResources(packagePath);
            if (urls != null && urls.hasMoreElements()) {
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    List<String> tmpList = null;
                    if (url != null) {
                        String type = url.getProtocol();
                        if (type.equals("file")) {
                            tmpList = getClassNameByFile(url.getPath(), null, childPackage);
                        } else if (type.equals("jar") && scanJar) {
                            tmpList = findClassNameFromNestedJar(url, packagePath, childPackage, scanJar, deepth);
                        }
                    }
                    if (tmpList != null && !tmpList.isEmpty()) {
                        fileNames.addAll(tmpList);
                    }
                }
            } else {
                List<String> tmpList = getClassNameByJars(((URLClassLoader) loader).getURLs(),
                        packagePath, childPackage, scanJar, deepth);
                if (tmpList != null && !tmpList.isEmpty()) {
                    fileNames.addAll(tmpList);
                }
            }
        } catch (Exception ex) {
        }

        fileNames = fileNames.stream()
                .map(x -> x.replace("BOOT-INF.classes.", ""))
                .collect(Collectors.toList());

        return fileNames;
    }

    /**
     * 从项目文件获取某包下所有类
     *
     * @param filePath     文件路径
     * @param className    类名集合
     * @param childPackage 是否遍历子包
     * @return 类的完整名称
     */
    private static List<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) {
        List<String> myClassName = new ArrayList<>();
        File file = new File(filePath);
        File[] childFiles = file.listFiles();
        final String classesSeparator = "classes" + fileSeparator;
        for (File childFile : childFiles) {
            if (childFile.isDirectory()) {
                //遍历子包中的类。
                if (childPackage) {
                    myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, childPackage));
                }
            } else {
                String childFilePath = childFile.getPath();
                if (childFilePath.endsWith(".class")) {
                    int index = childFilePath.lastIndexOf(classesSeparator);
                    if (index != -1) {
                        childFilePath = childFilePath.substring(index + classesSeparator.length(), childFilePath.length() - 6);
                        childFilePath = childFilePath.replace(fileSeparator, ".");
                        myClassName.add(childFilePath);
                    }
                }
            }
        }

        return myClassName;
    }

    /**
     * 从jar获取某包下所有类
     *
     * @param jarPath      jar文件路径
     * @param childPackage 是否遍历子包
     * @param scanJar      是否遍历到jar包中
     * @param deepth       遍历jar包的深度。
     * @return 类的完整名称
     */
    private static List<String> getClassNameByJar(String jarPath, String packagePath, boolean childPackage, boolean scanJar, int deepth) {
        List<String> myClassName = new ArrayList<>();
        String[] jarInfo = jarPath.split("!");
        String jarFilePath = jarInfo[0].substring(jarInfo[0].indexOf("/") + 1);
        try (JarFile jarFile = new JarFile(jarFilePath);) {
            Enumeration<JarEntry> entrys = jarFile.entries();
            while (entrys.hasMoreElements()) {
                JarEntry jarEntry = entrys.nextElement();
                String entryName = jarEntry.getName();
                if (entryName.endsWith(".jar") && scanJar && (deepth < 0 || deepth - 1 > 0)) {
                    URL url = new URL(jarEntry.getName());
                    myClassName.addAll(findClassNameFromNestedJar(url, packagePath, childPackage, scanJar, deepth - 1));
                    continue;
                } else if (entryName.endsWith(".class")) {
                    if (!entryName.startsWith(packagePath))
                        continue;
                    //不允许遍历子包，则返回。
                    if (!childPackage
                            && entryName.lastIndexOf("/") >= 0
                            && !entryName.substring(0, entryName.lastIndexOf("/")).equals(packagePath)) {
                        continue;
                    }
                    getClassNameByEntryName(entryName, packagePath, childPackage, myClassName);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return myClassName;
    }

    /**
     * 从jar获取某包下所有类
     *
     * @param jarUrl       资源URL
     * @param childPackage 是否遍历子包
     * @param scanJar      是否遍历到jar包中
     * @param deepth       遍历jar包的深度。
     * @return 类的完整名称
     */
    private static List<String> findClassNameFromNestedJar(URL jarUrl, String packagePath,
                                                           boolean childPackage,
                                                           boolean scanJar,
                                                           int deepth) {
        List<String> myClassName = new ArrayList<>();
        try {
            JarURLConnection jarConn = ((JarURLConnection) (jarUrl.openConnection()));
            jarConn.setUseCaches(false);
            Enumeration<JarEntry> entrys = jarConn.getJarFile().entries();
            while (entrys.hasMoreElements()) {
                JarEntry jarEntry = entrys.nextElement();
                String entryName = jarEntry.getName();
                if (entryName.endsWith(".jar") && scanJar && (deepth < 0 || deepth - 1 > 0)) {
                    URL url = new URL(jarEntry.getName());
                    myClassName.addAll(findClassNameFromNestedJar(url, packagePath, childPackage, scanJar, deepth - 1));
                    continue;
                } else if (entryName.endsWith(".class")) {
                    if (!entryName.startsWith(packagePath))
                        continue;
                    //不允许遍历子包，则返回。
                    if (!childPackage
                            && entryName.lastIndexOf("/") >= 0
                            && !entryName.substring(0, entryName.lastIndexOf("/")).equals(packagePath)) {
                        continue;
                    }
                    getClassNameByEntryName(entryName, packagePath, childPackage, myClassName);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return myClassName;
    }

    private static void getClassNameByEntryName(String entryName, String packagePath, boolean childPackage, List<String> classNames) {
        if (entryName.endsWith(".class")) {
            entryName = entryName.replace("/", ".")
                    .substring(0, entryName.lastIndexOf("."));
            classNames.add(entryName);
        }
    }


    /**
     * 从所有jar中搜索该包，并获取该包下所有类
     *
     * @param urls         URL集合
     * @param packagePath  包路径
     * @param childPackage 是否遍历子包
     * @param scanJar      是否遍历到jar包中
     * @param deepth       遍历jar包的深度。
     * @return 类的完整名称
     */
    private static List<String> getClassNameByJars(URL[] urls,
                                                   String packagePath, boolean childPackage, boolean scanJar, int deepth) {
        List<String> myClassName = new ArrayList<String>();
        if (urls != null) {
            for (int i = 0; i < urls.length; i++) {
                URL url = urls[i];
                String urlPath = url.getPath();
                // 不必搜索classes文件夹
                if (urlPath.endsWith("classes" + fileSeparator)) {
                    continue;
                }
                String jarPath = urlPath + "!" + fileSeparator + packagePath;
                myClassName.addAll(getClassNameByJar(jarPath, packagePath, childPackage, scanJar, deepth));
            }
        }
        return myClassName;
    }

    /**
     * 查找指定packageName包下的所有类上标注的annotationClazz注解实例
     *
     * @param packageName
     * @param annotationClazz
     * @return
     */
    public static <T extends Annotation> List<T> findAnnotionInstance(String packageName, Class<T> annotationClazz) {
        Vector<T> resultList = new Vector<>();

        List<String> clsNameList = getClassName(packageName);
        clsNameList.stream().parallel().forEach(x -> {
            try {
                Class clazz = Class.forName(x);
                T annotation = (T) clazz.getAnnotation(annotationClazz);
                if (annotation != null) {
                    resultList.add(annotation);
                }
            } catch (Exception ex) {
            }
        });

        return resultList;
    }

    /**
     * 在指定packageName包下所有标注了annotationOfClazz注解的类中，
     * 查找同时被public、static、final修饰的，且标注有annotationOfField注解的字段，
     * 返回其Map.Key=字段值,Map.Value=annotationOfField实例对象。
     *
     * @param packageName
     * @param annotationOfClazz
     * @param annotationOfField
     * @param <T>
     * @param <K>
     * @return
     */
    public static <T extends Annotation, K extends Annotation> Map<Object, K> findAnnotionInstance(String packageName,
                                                                                                   Class<T> annotationOfClazz,
                                                                                                   Class<K> annotationOfField) {
        Hashtable<Object, K> resultMap = new Hashtable<>();

        List<String> clsNameList = getClassName(packageName);
        clsNameList.stream().parallel().forEach(x -> {
            try {
                Class clazz = Class.forName(x);
                Object obj = clazz.getAnnotation(annotationOfClazz);
                if (obj != null) {
                    List<Field> fields = ReflectHelper.getPublicStaticFinalField(clazz, annotationOfField);
                    if (fields != null && fields.size() > 0) {
                        fields.stream().parallel().forEach(y -> {
                            try {
                                Object value = y.get(clazz);
                                K annotation = y.getAnnotation(annotationOfField);
                                resultMap.put(value, annotation);
                            } catch (Exception ex) {
                            }
                        });
                    }
                }
            } catch (Exception ex) {
            }
        });

        return resultMap;
    }

    /*
    public static List<ScopeInfo> findResourceIdDescriptorInfo(String packageName, Class<? extends Annotation> annotationClazz){
        Vector<ScopeInfo> resultList = new Vector<>();
        List<String> clsNameList = getClassName(packageName);

        ResourceIdDescriptorParser rParser = new ResourceIdDescriptorParser(ResourceIdDescriptor.class);
        ScopeDescriptorParser sParser = new ScopeDescriptorParser(ScopeDescriptor.class);
        clsNameList.stream().parallel().forEach(x->{
            try {
                Class clazz = Class.forName(x);
                if (clazz.getAnnotation(annotationClazz) != null){
                    List<Field> fields = ReflectHelper.getPublicStaticFinalField(clazz, ResourceIdDescriptor.class);
                    if (fields != null && fields.size() > 0 ){
                        fields.stream().forEach(y->{
                            try {
                                Object value = y.get(clazz);
                                String title = rParser.getTitle(clazz, y.getName(), value.toString());
                                String description = rParser.getDescription(clazz, y.getName(), value.toString());
                                int order = rParser.getOrder(clazz, y.getName());
                                boolean defaultSelected = rParser.getDefaultSelected(clazz, y.getName());
                                String[] scope = rParser.getScope(clazz, y.getName());

                                //资源ID信息。
                                ResourceInfo ri = new ResourceInfo(value, title, description, order, defaultSelected, scope);

                                //创建ScopeInfo信息。
                                Stream.of(scope).forEach(k -> {
                                    Optional<ScopeInfo> optional = resultList.stream().filter(z -> Objects.equals(z.getValue(), k)).findFirst();
                                    ScopeInfo si = null;
                                    if (!optional.isPresent()) {
                                        si = new ScopeInfo(k);
                                        resultList.add(si);
                                    } else {
                                        si = optional.get();
                                    }
                                    si.getResourceInfos().add(ri);
                                });
                            }catch (Exception ex){}
                        });
                    }
                }
            }catch (Exception ex){}
        });
        return resultList;
    }*/

}
